package com.shxzz.easy12306.services.ticket.service.handler.base;

import cn.hutool.core.collection.CollUtil;
import com.shxzz.easy12306.framework.starter.base.ApplicationContextHolder;
import com.shxzz.easy12306.framework.starter.convention.exceptions.ServiceException;
import com.shxzz.easy12306.framework.starter.designpattern.strategy.AbstractExecuteStrategy;
import com.shxzz.easy12306.services.ticket.dao.entity.SeatCountDO;
import com.shxzz.easy12306.services.ticket.dto.domain.PurchaseTicketPassengerDetailDTO;
import com.shxzz.easy12306.services.ticket.service.SeatService;
import com.shxzz.easy12306.services.ticket.service.handler.dto.SeatSelectReqDTO;
import com.shxzz.easy12306.services.ticket.service.handler.dto.SeatSelectRespDTO;
import com.shxzz.easy12306.services.ticket.service.handler.select.SeatSelectUtil;
import org.springframework.boot.CommandLineRunner;

import java.util.ArrayList;
import java.util.List;

/**
 * 火车购票抽象模板类
 *
 * @author shxzz
 * @create 2024-03-07-21:31
 */
public abstract class AbstractTrainPurchaseTicketTemplate implements PurchaseTicket, CommandLineRunner, AbstractExecuteStrategy<SeatSelectReqDTO, List<SeatSelectRespDTO>> {
    private SeatService seatService;

    /**
     * 座位号格式模板
     */
    private static final String SEAT_NUMBER_TEMPLATE = "%02d%s";

    @Override
    public List<SeatSelectRespDTO> executeResp(SeatSelectReqDTO requestParam) {
        return select(requestParam);
    }

    /**
     * 根据座位编码获取对应列索引
     */
    protected abstract int getColumnIndex(Character code);

    /**
     * 根据座位索引获取对应列编码
     */
    protected abstract char getColumnCode(int colIndex);

    /**
     * 获取每行座位数量
     */
    protected abstract int getSeatsPerRow();

    /**
     * 获取座位行数
     */
    protected abstract int getRowNum();

    public List<SeatSelectRespDTO> select(SeatSelectReqDTO requestParam) {
        List<SeatCountDO> seatCountDOList = seatService.countCarriageRemainTicketBySeatType(requestParam.getTrainId(), requestParam.getSeatType(), requestParam.getDeparture(), requestParam.getArrival());
        int allSeats = seatCountDOList.stream().mapToInt(SeatCountDO::getAvailableSeatNum).sum();
        int maxCarriageSeats = seatCountDOList.stream().mapToInt(SeatCountDO::getAvailableSeatNum).max().orElse(0);
        int passengerNum = requestParam.getPassengerSeatDetails().size();
        if (allSeats < passengerNum) {
            throw new ServiceException("座位余量不足");
        }

        if (maxCarriageSeats < passengerNum) {
            return selectRandomSeats(requestParam, seatCountDOList);
        }

        List<SeatCountDO> availableCarriageList = seatCountDOList.stream().filter(each -> each.getAvailableSeatNum() >= passengerNum).toList();
        List<String> chooseSeats = requestParam.getChooseSeats();
        if (CollUtil.isNotEmpty(chooseSeats)) {
            return selectMatchSeats(chooseSeats, availableCarriageList, requestParam);
        }
        if (passengerNum > getSeatsPerRow()) {
            return selectComplexSeats(requestParam, availableCarriageList);
        } else {
            return selectSeats(requestParam, availableCarriageList);
        }
    }

    /**
     * 全列车随机选择
     */
    private List<SeatSelectRespDTO> selectRandomSeats(SeatSelectReqDTO requestParam, List<SeatCountDO> seatCountDOList) {
        String trainId = requestParam.getTrainId();
        String departure = requestParam.getDeparture();
        String arrival = requestParam.getArrival();
        Integer seatType = requestParam.getSeatType();

        int passengerNum = requestParam.getPassengerSeatDetails().size();
        List<SeatSelectRespDTO> result = new ArrayList<>(passengerNum);
        int count = 0;
        for (SeatCountDO seatCountDO : seatCountDOList) {
            List<String> availableSeats = seatService.listAvailableSeats(trainId, seatCountDO.getCarriageNumber(), seatType, departure, arrival);
            int[] seatLayout = getSeatLayout(availableSeats);
            int needNum = Math.min(passengerNum - count, availableSeats.size());
            // 选择所需座位
            List<int[]> selected = SeatSelectUtil.noAdjacent(seatLayout, needNum);
            // 多次构建响应
            result.addAll(buildSeatSelectResp(selected, requestParam, seatCountDO.getCarriageNumber(), count));
            count += needNum;
            if (count == passengerNum) break;
        }
        return result;
    }

    /**
     * 同一车厢选择座位，两两一组进行选择
     */
    private List<SeatSelectRespDTO> selectComplexSeats(SeatSelectReqDTO requestParam, List<SeatCountDO> seatCountDOList) {
        String trainId = requestParam.getTrainId();
        String departure = requestParam.getDeparture();
        String arrival = requestParam.getArrival();
        Integer seatType = requestParam.getSeatType();

        int passengerNum = requestParam.getPassengerSeatDetails().size();
        int selectSeatGroup = (passengerNum + 1) / 2;   // 分组数量
        List<int[]> selected = new ArrayList<>();
        int[] lastSeatLayout;
        int[] deepCopyLastSeatLayout = new int[0];
        String lastCarriageNumber = null;
        for (SeatCountDO seatCountDO : seatCountDOList) {
            int count = 0;
            List<String> availableSeats = seatService.listAvailableSeats(trainId, seatCountDO.getCarriageNumber(), seatType, departure, arrival);
            lastCarriageNumber = seatCountDO.getCarriageNumber();
            lastSeatLayout = getSeatLayout(availableSeats);
            deepCopyLastSeatLayout = SeatSelectUtil.deepCopySeatLayout(lastSeatLayout);
            // 两两一组选择连续座位
            for (int i = 0; i < selectSeatGroup; i++) {
                List<int[]> groupSelect = SeatSelectUtil.adjacent(lastSeatLayout, Math.min(2, passengerNum - count));
                count += groupSelect.size();
                SeatSelectUtil.setSelectSeatUnavailable(lastSeatLayout, groupSelect);
                selected.addAll(groupSelect);
            }
            if (selected.size() == passengerNum) break;
        }
        // 不满足条件，最后遍历车厢随机选择
        if (selected.size() != passengerNum) {
            selected = SeatSelectUtil.noAdjacent(deepCopyLastSeatLayout, passengerNum);
        }
        return buildSeatSelectResp(selected, requestParam, lastCarriageNumber, 0);
    }

    /**
     * 同一车厢选择座位，优先选择满足座位布局的座位
     */
    private List<SeatSelectRespDTO> selectMatchSeats(List<String> chooseSeats, List<SeatCountDO> seatCountDOList, SeatSelectReqDTO requestParam) {
        // 构建座位布局模板
        int[] filter = new int[2];
        for (String choose : chooseSeats) {
            int col = getColumnIndex(choose.charAt(0));
            int row = choose.charAt(1) - '0';
            filter[row] |= 1 << col;
        }

        String trainId = requestParam.getTrainId();
        String departure = requestParam.getDeparture();
        String arrival = requestParam.getArrival();
        Integer seatType = requestParam.getSeatType();

        int passengerNum = requestParam.getPassengerSeatDetails().size();
        List<int[]> selected = null;
        int[] lastSeatLayout = new int[0];
        String lastCarriageNumber = null;
        for (SeatCountDO seatCountDO : seatCountDOList) {
            List<String> availableSeats = seatService.listAvailableSeats(trainId, seatCountDO.getCarriageNumber(), seatType, departure, arrival);
            lastCarriageNumber = seatCountDO.getCarriageNumber();
            lastSeatLayout = getSeatLayout(availableSeats);
            // 根据座位布局选取
            selected = SeatSelectUtil.selectSeatByFilter(lastSeatLayout, filter);
            if (selected.size() == passengerNum) break;
        }

        // 没有符合座位布局的，最后一个车厢选取
        if (selected == null || selected.size() != passengerNum) {
            selected = SeatSelectUtil.noAdjacent(lastSeatLayout, passengerNum);
        }

        return buildSeatSelectResp(selected, requestParam, lastCarriageNumber, 0);
    }

    /**
     * 同一车厢选择座位，优先选择连续的座位
     */
    private List<SeatSelectRespDTO> selectSeats(SeatSelectReqDTO requestParam, List<SeatCountDO> seatCountDOList) {
        String trainId = requestParam.getTrainId();
        String departure = requestParam.getDeparture();
        String arrival = requestParam.getArrival();
        Integer seatType = requestParam.getSeatType();

        int passengerNum = requestParam.getPassengerSeatDetails().size();
        List<int[]> selected = null;
        int[] lastSeatLayout = new int[0];
        String latCarriageNumber = null;
        for (SeatCountDO seatCountDO : seatCountDOList) {
            List<String> availableSeats = seatService.listAvailableSeats(trainId, seatCountDO.getCarriageNumber(), seatType, departure, arrival);
            latCarriageNumber = seatCountDO.getCarriageNumber();
            lastSeatLayout = getSeatLayout(availableSeats);
            // 选择连续的座位
            selected = SeatSelectUtil.adjacent(lastSeatLayout, passengerNum);
            if (selected.size() == passengerNum) break;
        }
        // 连续条件不满足，最后遍历车厢随机选择
        if (selected == null || selected.size() != passengerNum) {
            selected = SeatSelectUtil.noAdjacent(lastSeatLayout, passengerNum);
        }
        return buildSeatSelectResp(selected, requestParam, latCarriageNumber, 0);
    }

    /**
     * 构建座位选择结果
     *
     * @param startIndex 乘车人列表的起始索引，用于多次构建结果
     */
    private List<SeatSelectRespDTO> buildSeatSelectResp(List<int[]> selected, SeatSelectReqDTO requestParam, String carriageNumber, Integer startIndex) {
        List<SeatSelectRespDTO> result = new ArrayList<>();
        List<PurchaseTicketPassengerDetailDTO> passengerSeatDetails = requestParam.getPassengerSeatDetails();
        for (int i = 0; i < selected.size(); i++) {
            int[] seat = selected.get(i);
            SeatSelectRespDTO seatSelectRespDTO = SeatSelectRespDTO.builder()
                    .carriageNumber(carriageNumber)
                    .seatNumber(String.format(SEAT_NUMBER_TEMPLATE, seat[0], getColumnCode(seat[1])))
                    .seatType(requestParam.getSeatType())
                    .passengerId(passengerSeatDetails.get(i + startIndex).getPassengerId())
                    .build();
            result.add(seatSelectRespDTO);
        }
        return result;
    }

    private int[] getSeatLayout(List<String> availableSeats) {
        int[] layout = new int[getRowNum() + 1];
        availableSeats.forEach(seat -> {
            int row = Integer.parseInt(seat.substring(0, 2));
            int col = getColumnIndex(seat.charAt(2));
            layout[row] |= 1 << col;
        });
        return layout;
    }

    @Override
    public void run(String... args) throws Exception {
        seatService = ApplicationContextHolder.getBean(SeatService.class);
    }
}
